<template>
  <div class="model">
    <div ref="modelBox"></div>
    <div class="scene_tips" v-show="tipShow" :style="getPosition">
      <div class="scene_tipsall">{{ tipName }}</div>
    </div>
  </div>
  <!-- <img v-if="loading" class="load" src="@/assets/experiment/modelLoad.gif" alt="" /> -->
</template>

<script>
import * as THREE from 'three'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js'
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'
import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader'
import gsap from 'gsap'
// 伽马校正后处理Shader
import { GammaCorrectionShader } from 'three/examples/jsm/shaders/GammaCorrectionShader.js'
export default {
  props: {
    modelUrl: {
      type: String,
      required: true
    },
    modelDrag: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      tipName: null,
      baseUrl: 'http://zfatt.oss-cn-beijing.aliyuncs.com/',
      loading: false,
      modelBox: null,
      scene: null,
      camera: null,
      renderer: null,
      controls: null,
      clock: null,
      composer: null,
      outlinePass: null,
      model: null,
      animations: null,
      url: null,
      modelSize: null,
      mixer: null,
      action: null,
      actionState: false,
      splittingState: false,
      combinationState: false,
      selectedObject: null,
      isDragging: false,
      tipShow: false,
      tipPosition: {
        x: 0,
        y: 0
      },
      previousMousePosition: { x: 0, y: 0 },
      debounceTimeout: null
    }
  },
  methods: {
    init() {
      this.initScene()
      this.initCamera()
      this.initLight()
      this.initRenderer()
      this.initFile()
    },
    initScene() {
      this.scene = new THREE.Scene()
      this.clock = new THREE.Clock()
    },
    initCamera() {
      this.loading = true
      this.camera = new THREE.PerspectiveCamera(
        75,
        this.$el.clientWidth / this.$el.clientHeight,
        0.1,
        1000
      )
    },
    initRenderer() {
      this.renderer = new THREE.WebGLRenderer({
        alpha: true,
        antialias: true,
        preserveDrawingBuffer: true
      })
      // 确保渲染器的宽度和高度不为 0
      const width = Math.max(1, window.innerWidth)
      const height = Math.max(1, window.innerHeight)
      this.renderer.setSize(width, height)
      this.renderer.setPixelRatio(Math.min(window.devicePixelRatio * 2, 2)) // 限制最大像素比

      this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
      this.renderer.shadowMap.enabled = true
      this.renderer.toneMapping = THREE.ACESFilmicToneMapping
      this.renderer.toneMappingExposure = 1.6
      this.renderer.outputColorSpace = THREE.SRGBColorSpace
      this.renderer.render(this.scene, this.camera)

      this.createRenderChannel()
      this.renderer.domElement.addEventListener('dblclick', this.onDoubleClick)
      this.renderer.domElement.addEventListener('mousedown', this.onMouseDown)
      this.renderer.domElement.addEventListener('mousemove', this.onMouseMove)
      this.renderer.domElement.addEventListener('mouseup', this.onMouseUp)

      // 添加触摸事件监听器
      this.renderer.domElement.addEventListener('touchstart', this.onTouchStart)
      this.renderer.domElement.addEventListener('touchmove', this.onTouchMove)
      this.renderer.domElement.addEventListener('touchend', this.onTouchEnd)

      this.$el.appendChild(this.renderer.domElement)
      window.addEventListener('resize', this.onWindowResize) // 监听窗口变化
    },
    // 创建渲染通道
    createRenderChannel() {
      this.composer = new EffectComposer(this.renderer)
      const width = Math.max(1, this.$el.clientWidth || window.innerWidth) // 确保宽度不为 0
      const height = Math.max(1, this.$el.clientHeight || window.innerHeight) // 确保高度不为 0
      this.composer.setSize(width, height)

      const renderPass = new RenderPass(this.scene, this.camera)
      this.composer.addPass(renderPass)
      this.outlinePass = new OutlinePass(
        new THREE.Vector2(width, height),
        this.scene,
        this.camera
      )
      this.outlinePass.pulsePeriod = 2
      this.outlinePass.renderToScreen = true
      this.outlinePass.edgeGlow = 1
      this.outlinePass.edgeStrength = 4
      this.outlinePass.edgeThickness = 4
      this.outlinePass.visibleEdgeColor.set('#ff0000')
      this.outlinePass.hiddenEdgeColor.set('#ff0000')
      this.outlinePass.selectedObjects = []
      this.composer.addPass(this.outlinePass)

      const fxaaPass = new ShaderPass(FXAAShader)
      fxaaPass.uniforms['resolution'].value.set(
        1 / Math.max(1, this.$el.clientWidth),
        1 / Math.max(1, this.$el.clientHeight)
      )
      this.composer.addPass(fxaaPass)

      const gammaPass = new ShaderPass(GammaCorrectionShader)
      this.composer.addPass(gammaPass)
    },
    initLight() {
      const light = new THREE.AmbientLight(0xffffff, 0.8)
      this.scene.add(light)

      const directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.5)
      directionalLight1.position.set(0.5, 2, 1)
      directionalLight1.position.set(0.05, -0.65, 2.7)
      directionalLight1.castShadow = false
      this.scene.add(directionalLight1)
      const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.5)
      directionalLight2.position.set(-11.2, -4.6, -2)
      directionalLight2.castShadow = false
      this.scene.add(directionalLight2)

      const pointLight = new THREE.PointLight(0xffffff, 1, 100)
      pointLight.position.set(5, 5, 5)
      this.scene.add(pointLight)
    },
    animate() {
      if (this.composer) {
        this.composer.render()
      }
      if (this.resizeRendererToDisplaySize(this.renderer)) {
        const canvas = this.renderer.domElement
        this.camera.aspect = canvas.clientWidth / canvas.clientHeight
        this.camera.updateProjectionMatrix()
      }
      //有动画版本
      // const deltaTime = this.clock.getDelta()
      // if (this.mixer) {
      //   this.mixer.update(deltaTime)
      // }
      requestAnimationFrame(this.animate)
    },
    resizeRendererToDisplaySize(renderer) {
      const canvas = renderer.domElement
      const width = Math.max(1, canvas.clientWidth || window.innerWidth) // 确保宽度不为 0
      const height = Math.max(1, canvas.clientHeight || window.innerHeight) // 确保高度不为 0
      const needResize = canvas.width !== width || canvas.height !== height
      if (needResize) {
        renderer.setSize(width, height, false)
      }
      return needResize
    },
    initControls(x, y, z) {
      this.controls = new OrbitControls(this.camera, this.renderer.domElement)
      this.controls.target.set(x, y, z)
      this.controls.enablePan = false
      this.controls.mouseButtons = {
        LEFT: THREE.MOUSE.PAN,
        MIDDLE: THREE.MOUSE.DOLLY,
        RIGHT: THREE.MOUSE.ROTATE
      }
      this.controls.update()
      this.$emit('loadModelOver')
    },
    initFile() {
      if (this.url === this.modelUrl) return false
      const gltf_loader = new GLTFLoader()
      const dracoLoader = new DRACOLoader()
      dracoLoader.setDecoderPath(
        'http://zfatt.oss-cn-beijing.aliyuncs.com/threejs/draco/'
      )
      dracoLoader.preload()
      gltf_loader.setDRACOLoader(dracoLoader)
      gltf_loader.load(
        this.baseUrl + this.modelUrl,
        (glat) => {
          let newModel
          this.url = this.modelUrl
          if (this.model && newModel === this.model) return false
          if (this.model) {
            newModel = glat.scene
            this.scene.remove(this.model)
            this.disposeModel(this.model)
          }
          this.model = glat.scene

          const textureCube = new THREE.TextureLoader().load(
            'https://zfatt.oss-cn-beijing.aliyuncs.com/experiment/fydw/TaiDeng_C_BaseMap.png',
            () => {
              textureCube.mapping = THREE.EquirectangularReflectionMapping
              this.model.traverse((child) => {
                // 设置初始位置
                child.localToWorld(child.position) //转为世界坐标

                child.startPoisition = child.position.clone()
                // 完成
                if (child instanceof THREE.Mesh) {
                  child.userData.draggable = true
                }
                // 设置金属
                if (child.isMesh) {
                  child.material.metalness = 0.9
                  child.material.roughness = 0.5
                  child.material.envMap = textureCube
                  child.material.envMapIntensity = 1.2
                  child.material.clearcoat = 1
                  child.material.clearcoatRoughness = 0.01
                }
              })
              // 设置玻璃
              // const mesh = glat.scene.getObjectByName('XXianGuan')
              // mesh.material = new THREE.MeshPhysicalMaterial({
              //   metalness: 0, //玻璃非金属
              //   roughness: 0, //玻璃表面光滑
              //   envMap: textureCube, //环境贴图
              //   envMapIntensity: 0.8, //环境贴图对Mesh表面影响程度
              //   transmission: 1, //玻璃材质透光率，transmission替代opacity
              //   ior: 1 //折射率
              // })

              const boundingBox = new THREE.Box3().setFromObject(this.model)
              this.modelSize = boundingBox.getSize(new THREE.Vector3()).length()
              const fov = this.camera.fov * (Math.PI / 180)
              const cameraDistance =
                this.modelSize / (2 * Math.tan(fov / 2)) + 0.5
              const target = boundingBox.getCenter(new THREE.Vector3())

              this.camera.position.set(
                target.x,
                target.y,
                target.z + cameraDistance
              )
              this.camera.lookAt(target)

              if (glat.animations.length) {
                this.animations = glat.animations
                this.createAnimation()
              }

              if (newModel) {
                this.scene.add(newModel)
                this.controls.target.set(target.x, target.y, target.z)
                this.controls.update()
              } else {
                this.scene.add(this.model)
                this.animate()
                this.initControls(target.x, target.y, target.z)
              }
              this.loading = false
            }
          )
        },
        undefined,
        (error) => {
          console.error(error)
        }
      )
    },

    disposeModel(model) {
      model.traverse((object) => {
        if (object.isMesh) {
          object.geometry.dispose()
          if (object.material.map) {
            object.material.map.dispose()
          }
        }
      })
      this.actionState = false
      this.splittingState = false
      this.combinationState = false
    },
    onMouseDown(event) {
      if (event.button !== 0) return false
      event.preventDefault()
      this.onMouseClick(event)
      this.isDragging = true
      // 记录鼠标按下时的位置
      this.previousMousePosition = {
        x: event.clientX,
        y: event.clientY
      }
    },
    onMouseMove(event) {
      if (event.button !== 0) return false
      event.preventDefault()
      if (this.isDragging && this.selectedObject) {
        // 计算鼠标移动的距离
        const deltaMove = {
          x: event.clientX - this.previousMousePosition.x,
          y: event.clientY - this.previousMousePosition.y
        }
        // 计算相机的方向
        const cameraDirection = new THREE.Vector3()
        // 获取相机的方向
        this.camera.getWorldDirection(cameraDirection)
        // 计算相机的右向量
        const movementDirection = new THREE.Vector3(
          deltaMove.x,
          -deltaMove.y,
          0
        )
        // 计算相机的右向量
        movementDirection.applyQuaternion(this.camera.quaternion)
        let ratio = null
        if (this.modelSize < 0.3) {
          ratio = this.modelSize / 220
        } else if (this.modelSize < 0.5) {
          ratio = this.modelSize / 300
        } else if (this.modelSize < 1) {
          ratio = this.modelSize / 500
        } else if (this.modelSize < 1.5) {
          ratio = this.modelSize / 800
        } else if (this.modelSize < 1.7) {
          ratio = this.modelSize / 840
        } else {
          ratio = this.modelSize / (this.modelSize * 300)
        }

        // 计算移动的距离
        this.selectedObject.position.add(
          movementDirection.multiplyScalar(ratio)
        )
        this.previousMousePosition = {
          x: event.clientX,
          y: event.clientY
        }
      }
      // 增加触摸判断
      if (this.model) {
        // 防抖节流处理
        if (this.debounceTimeout) {
          clearTimeout(this.debounceTimeout)
        }
        this.debounceTimeout = setTimeout(() => {
          // 更新射线的位置和方向
          // 计算鼠标位置的归一化设备坐标
          const getBoundingClientRect = this.$el.getBoundingClientRect()
          const mouse = new THREE.Vector2()
          mouse.x =
            ((event.clientX - getBoundingClientRect.left) /
              this.$el.offsetWidth) *
              2 -
            1
          mouse.y =
            -(
              (event.clientY - getBoundingClientRect.top) /
              this.$el.offsetHeight
            ) *
              2 +
            1
          const raycaster = new THREE.Raycaster()
          // 将鼠标位置转换为世界坐标
          raycaster.setFromCamera(mouse, this.camera)

          // 检测与模型相交的子对象
          const intersects = raycaster.intersectObjects(
            this.model.children,
            true
          )
          this.tipShow = false
          if (intersects.length > 0) {
            // 获取第一个相交的对象组
            const intersectsParent = intersects[0].object.name.split('_')[0]
            this.selectedObject = this.model.getObjectByName(intersectsParent)
            const matchedItem = this.modelDrag.find(
              (item) => item.id == this.selectedObject.name
            )
            console.log('matchedItem', matchedItem, this.modelDrag)

            if (matchedItem) {
              this.tipPosition = {
                x: event.offsetX,
                y: event.offsetY
              }
              this.tipShow = true
              this.tipName = matchedItem.name
            } else {
              this.tipShow = false
              this.tipName = null
            }
          } else {
            this.tipShow = false
            this.tipName = null
          }
        }, 100) // 节流时间为100ms
      }
    },
    // 鼠标抬起事件
    onMouseUp(event) {
      event.preventDefault()
      if (event.button !== 0) return false
      event.target.style.cursor = 'default'
      this.isDragging = false
    },
    // 双击事件 切换选中的模型
    onDoubleClick() {
      if (this.selectedObject) {
        this.$emit('toggleModel', this.selectedObject)
      }
    },
    onMouseClick(event) {
      if (event.button !== 0 || !this.model) {
        return false
      }
      // 计算鼠标位置的归一化设备坐标
      const getBoundingClientRect = this.$el.getBoundingClientRect()
      const mouse = new THREE.Vector2()
      mouse.x =
        ((event.clientX - getBoundingClientRect.left) / this.$el.offsetWidth) *
          2 -
        1
      mouse.y =
        -((event.clientY - getBoundingClientRect.top) / this.$el.offsetHeight) *
          2 +
        1

      // 更新射线的位置和方向
      const raycaster = new THREE.Raycaster()
      // 将鼠标位置转换为世界坐标
      raycaster.setFromCamera(mouse, this.camera)

      // 检测与模型相交的子对象
      const intersects = raycaster.intersectObjects(this.model.children, true)

      if (intersects.length > 0) {
        // 获取第一个相交的对象组
        const intersectsParent = intersects[0].object.name.split('_')[0]
        this.selectedObject = this.model.getObjectByName(intersectsParent)

        const disabledObject = ['DianLuBan'] // 禁止拖拽的模型
        // 判断选中的对象是否在禁止拖拽的模型列表中
        if (!disabledObject.includes(this.selectedObject.name)) {
          this.$emit('checkedModel', this.selectedObject)
          // 清空原先被选中的对象
          this.outlinePass.selectedObjects = []
          // 将选中的对象添加到selectedObjects数组中
          this.outlinePass.selectedObjects.push(this.selectedObject)
          if (this.selectedObject) {
            // event.target.style.cursor = 'move'
            //
          }
        } else {
          this.selectedObject = null
        }
      } else {
        this.outlinePass.selectedObjects = []
        this.selectedObject = null
      }
    },
    // 创建动画
    createAnimation() {
      this.mixer = new THREE.AnimationMixer(this.model)
      const animation = this.animations[0]
      this.action = this.mixer.clipAction(animation)
      this.action.loop = THREE.LoopOnce
      this.action.clampWhenFinished = true
    },
    // 组合
    combination() {
      if (this.actionState && !this.combinationState) {
        this.combinationState = true
        this.action.paused = false
        this.action.time = 2
        this.action.play()
        setTimeout(() => {
          this.actionState = false
          this.combinationState = false
        }, 1000)
      }
    },
    // 拆分

    splitting() {
      if (!this.actionState && !this.splittingState) {
        this.splittingState = true
        this.action.stop()
        this.action.time = 0.5
        this.action.play()
        setTimeout(() => {
          this.action.paused = true
          this.actionState = true
          this.splittingState = false
        }, 1000)
      }
    },
    // 重新设置模型尺寸
    setModelSize(type) {
      if (type === 'spread') {
        this.renderer.setSize(807, this.$el.clientHeight)
        this.camera.aspect = 807 / this.$el.clientHeight
      } else {
        this.renderer.setSize(1000, this.$el.clientHeight)
        this.camera.aspect = 1000 / this.$el.clientHeight
      }
      this.camera.updateProjectionMatrix()
    },
    //自定义拆解函数，用于将模型的子对象随机移动一定距离
    mergeHandle() {
      // 定义一个固定的移动距离
      const fixedDistances = [
        { x: 0.2, y: 0.2, z: 0.2 },
        { x: -0.2, y: -0.2, z: -0.2 },
        { x: 0.3, y: 0.1, z: -0.1 },
        { x: -0.1, y: 0.3, z: 0.1 }
      ]
      let index = 0 // 用于循环使用固定距离

      // 遍历模型的所有子对象
      this.model.traverse((child) => {
        console.log('child', child) // 打印当前遍历的子对象

        // 检查子对象是否有初始位置属性
        if (child.startPoisition) {
          // 判断当前子对象是否在 modelDrag 列表中
          const isDraggable = this.modelDrag.some(
            (item) => item.componentSign === child.name
          )
          if (!isDraggable) {
            // console.log(`子对象 ${child.name} 不允许移动`)
            return
          }

          // 获取固定的x、y、z方向的移动距离
          const { x, y, z } = fixedDistances[index]
          index = (index + 1) % fixedDistances.length // 循环使用固定距离

          // 使用gsap动画库将子对象移动到新的位置
          gsap.to(child.position, {
            x: child.startPoisition.x + x, // 新的x坐标
            y: child.startPoisition.y + y, // 新的y坐标
            z: child.startPoisition.z + z, // 新的z坐标
            duration: 1, // 动画持续时间为1秒
            ease: 'power2.inOut' // 动画缓动效果
          })
        }
      })
    },
    //还原函数
    resetModel() {
      this.model.traverse((child) => {
        if (child.startPoisition) {
          gsap.to(child.position, {
            x: child.startPoisition.x,
            y: child.startPoisition.y,
            z: child.startPoisition.z,
            duration: 1,
            ease: 'power2.inOut'
          })
        }
      })
    },
    getCamera() {
      return this.camera
    },
    //根据屏幕尺寸变化自适应
    onWindowResize() {
      //浏览器窗口大小改变后重新计算
      this.maincontWidth = this.$el.offsetWidth
      this.maincontHeight = this.$el.offsetHeight
      this.maincontSc = this.maincontWidth / this.maincontHeight
      this.camera.aspect = this.maincontSc
      this.camera.updateProjectionMatrix()
      this.renderer.setSize(this.maincontWidth, this.maincontHeight)
    },
    onTouchStart(event) {
      if (event.touches.length !== 1) return false // 只处理单指触摸
      event.preventDefault()
      const touch = event.touches[0]
      this.previousMousePosition = {
        x: touch.clientX,
        y: touch.clientY
      }
      this.isDragging = true
      this.onMouseClick({
        button: 0,
        clientX: touch.clientX,
        clientY: touch.clientY
      })
    },
    onTouchMove(event) {
      if (!this.isDragging || event.touches.length !== 1) return false
      event.preventDefault()
      const touch = event.touches[0]
      const deltaMove = {
        x: touch.clientX - this.previousMousePosition.x,
        y: touch.clientY - this.previousMousePosition.y
      }
      const cameraDirection = new THREE.Vector3()
      this.camera.getWorldDirection(cameraDirection)
      const movementDirection = new THREE.Vector3(deltaMove.x, -deltaMove.y, 0)
      movementDirection.applyQuaternion(this.camera.quaternion)
      let ratio = null
      if (this.modelSize < 0.3) {
        ratio = this.modelSize / 220
      } else if (this.modelSize < 0.5) {
        ratio = this.modelSize / 300
      } else if (this.modelSize < 1) {
        ratio = this.modelSize / 500
      } else if (this.modelSize < 1.5) {
        ratio = this.modelSize / 800
      } else if (this.modelSize < 1.7) {
        ratio = this.modelSize / 840
      } else {
        ratio = this.modelSize / (this.modelSize * 300)
      }
      if (this.selectedObject) {
        this.selectedObject.position.add(
          movementDirection.multiplyScalar(ratio)
        )
      }
      this.previousMousePosition = {
        x: touch.clientX,
        y: touch.clientY
      }
    },
    onTouchEnd(event) {
      event.preventDefault()
      this.isDragging = false
    }
  },
  mounted() {
    // this.$el = this.$refs.modelBox
    this.init()
  },
  computed: {
    getPosition() {
      if (!this.tipShow) {
        return 'display:none;'
      }
      //根据返回的位置信息计算物品提示小标位
      let maincontWidth = this.$el.offsetWidth
      let maincontHeight = this.$el.offsetHeight
      let leftPercentage = ((this.tipPosition.x + 10) / maincontWidth) * 100
      let topPercentage = ((this.tipPosition.y - 80) / maincontHeight) * 100

      return 'left:' + leftPercentage + '%;top:' + topPercentage + '%;'
    }
  }
}
</script>

<style scoped>
.model {
  height: 100%;
}
.load {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}
/* 器材提示框 */
.scene_tips {
  width: auto;
  height: 44px;
  text-align: center;
  line-height: 44px;
  box-sizing: border-box;
  padding: 0 10px;
  position: fixed;
  font-size: 18px;
  font-family: Source Han Sans SC;
  font-weight: 400;
  color: #ffffff;
  z-index: 4;
  background: #0888ffa4;
  border-radius: 8px;
}

.scene_tips::after {
  content: '';
  position: absolute;
  bottom: -20px;
  left: 20px;
  transform: translateX(-50%);
  width: 0;
  height: 0;
  border-width: 10px;
  border-style: solid;
  border-color: #0888ffa4 transparent transparent transparent;
  z-index: 3;
}

.scene_tips div {
  position: relative;
  z-index: 1;
  white-space: nowrap;
}
</style>
